﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using Infragistics.Olap;
using Infragistics.Olap.Data;
using Infragistics.Olap.Data.Base;
using PivotGridUsingFlatPivotTable.DataSource.Data;

namespace PivotGridUsingFlatPivotTable.DataSource
{
    public class FlatPivotDataProvider : DataProviderBase
    {
        private IConnectionSettings _connectionSettings;
        IDictionary<string, ILevel> columnLevels = new Dictionary<string, ILevel>();
        IDictionary<string, ILevel> rowLevels = new Dictionary<string, ILevel>();

        public FlatPivotDataProvider(FlatPivotDataConnectionSettings connectionSettings)
        {
            this._connectionSettings = connectionSettings;
        }

        public FlatPivotDataConnectionSettings FlatConnectionSettings
        {
            get { return (FlatPivotDataConnectionSettings)this.ConnectionSettings; }
        }

        public override IConnectionSettings ConnectionSettings
        {
            get { return this._connectionSettings; }
        }       

        public override void LoadCubesAsync(string catalogName)
        {
            OlapSchema schema = new OlapSchema();

            AddCube(schema, new Cube("FlatCube", string.Empty, "Flat Data Cube", string.Empty));
            this.OnLoadCubesCompleted(new LoadCubesCompletedEventArgs(schema, null, false, schema));
        }                

        public override void LoadDimensionsAsync(Infragistics.Olap.Data.ICube cube)
        {
            // can keep this hardcoded if your scenario has just one row and one column
            Dimension dimension1 = new Dimension("Columns", "[Columns]", "Columns", string.Empty);
            Dimension dimension2 = new Dimension("Rows", "[Rows]", "Rows", string.Empty);

            Hierarchy hierarchy1 = new Hierarchy("Columns", string.Format("{0}.[Columns]", dimension1.UniqueName), "Columns Hierarchy", string.Empty, string.Empty, string.Empty, dimension1, string.Empty, HierarchyOrigin.UserDefined);
            Hierarchy hierarchy2 = new Hierarchy("Rows", string.Format("{0}.[Rows]", dimension2.UniqueName), "Rows Hierarchy", string.Empty, string.Empty, string.Empty, dimension2, string.Empty, HierarchyOrigin.UserDefined);

            var flatPivotTable = this.FlatConnectionSettings.ServerDataSource.LoadData();

            // extract levels' metadata 
            this.CreateLevelsForAxis(flatPivotTable.XAxis, hierarchy1, columnLevels);
            this.CreateLevelsForAxis(flatPivotTable.YAxis, hierarchy2, rowLevels);
            
            AddDimension((Cube)cube, dimension1);
            AddDimension((Cube)cube, dimension2);

            AddHierarchy(dimension1, hierarchy1);
            AddHierarchy(dimension2, hierarchy2);

            foreach (ILevel value in columnLevels.Values)
            {
                AddLevel(hierarchy1, value);
            }

            foreach (ILevel value in rowLevels.Values)
            {
                AddLevel(hierarchy2, value);
            }

            // extract members' metadata
            this.CreateMembersForLevels(flatPivotTable.XAxis, null, 0, string.Empty, columnLevels);
            this.CreateMembersForLevels(flatPivotTable.YAxis, null, 0, string.Empty, rowLevels);

            this.OnLoadDimensionsCompleted(new AsyncCompletedEventArgs(null, false, cube));
        }

        public override void LoadMeasureGroupDimensionsAsync(Infragistics.Olap.Data.ICube cube)
        {
            this.OnLoadMeasureGroupDimensionsCompleted(new AsyncCompletedEventArgs(null, false, cube));
        }

        public override void LoadMeasureGroupsAsync(Infragistics.Olap.Data.ICube cube)
        {
            this.OnLoadMeasureGroupsCompleted(new AsyncCompletedEventArgs(null, false, cube));
        }

        public override void LoadMeasuresAsync(Infragistics.Olap.Data.ICube cube)
        {
            // can keep this hardcoded if for your scenario just one measure is supported
            AddMeasure((Cube)cube, new Measure("Measure", "Measure", "Measure", string.Empty, string.Empty, AggregatorType.None));
            this.OnLoadMeasuresCompleted(new AsyncCompletedEventArgs(null, false, cube));
        }

        public override void LoadMembersAsync(Infragistics.Olap.Data.ICube cube, Infragistics.Olap.Data.IHierarchy hierarchy)
        {
            this.OnLoadMembersCompleted(new AsyncCompletedEventArgs(null, false, hierarchy));
        }

        public override void LoadMembersAsync(Infragistics.Olap.Data.ICube cube, Infragistics.Olap.Data.ILevel level)
        {
            this.OnLoadMembersCompleted(new AsyncCompletedEventArgs(null, false, level));
        }

        public override void LoadMembersAsync(Infragistics.Olap.Data.ICube cube, Infragistics.Olap.Data.IFilterMember filterMember)
        {
            this.OnLoadMembersCompleted(new AsyncCompletedEventArgs(null, false, filterMember));
        }

        public override void LoadMembersAsync(Infragistics.Olap.Data.ICube cube, Infragistics.Olap.Data.IMember member)
        {
            //throw new NotImplementedException();
        }

        public override void LoadSchemaAsync()
        {
            OlapSchema schema = new OlapSchema();
            this.OnLoadSchemaCompleted(new LoadSchemaCompletedEventArgs(schema, null, false, schema));
        }

        /*
         * Data source calls this method when you have changed anything that will change the result
         * DataSourceState instance has kept the information about the change this new result should be aware about
         * Currenty we genereate simple static result.
         */
        internal IResult GenerateResult(DataSourceBase.DataSourceState sourceState)
        {
            var flatPivotTable = this.FlatConnectionSettings.ServerDataSource.LoadData();
            return this.BuildResultFromFlatPivotTable(flatPivotTable);
        }

        /*
		 * This conversion is very confusing to me, as its not completely clear (without reading
		 * complete OLAP specs) how the tuples, members and levels need to be organised in the axes
		 * to display correctly in the grid. I imagine its easy if you're connecting to an existing
		 * OLAP source (which the model presumably fits perfectly), but for custom sources such as
		 * mine it is less than perfectly clear.
		 */
        protected virtual IResult BuildResultFromFlatPivotTable(FlatPivotTable flatPivotTable)
        {
            var columnAxis = BuildResultAxis(flatPivotTable.XAxis, this.columnLevels);
            var rowAxis = BuildResultAxis(flatPivotTable.YAxis, this.rowLevels);
            var cells = BuildResultCells(flatPivotTable);
            return new Result(rowAxis, columnAxis, cells);
        }

        protected virtual IResultAxis BuildResultAxis(FlatPivotAxis flatPivotAxis, IDictionary<string, ILevel> levels)
        {
            var tuples = new List<ITuple>();
            this.ProcessFlatPivotAxis(flatPivotAxis, tuples, null, 0, String.Empty, levels);
            return new ResultAxis(tuples, true /* what does this really do? */);
        }

        /*
         * A bleak attempt at converting my axis into an IResultAxis...
         */
        protected virtual void ProcessFlatPivotAxis(FlatPivotAxis flatPivotAxis, IList<ITuple> tuples, IMember parentMember, int levelDepth, string uniqueNamePrefix, IDictionary<string, ILevel> levels)
        {
            // use the same pattern as when the member was created
            var uniqueName = (uniqueNamePrefix != String.Empty ? uniqueNamePrefix + "_" : String.Empty) + flatPivotAxis.Header;            

            string levelName = string.Format("Level{0}", flatPivotAxis.Level);
            ILevel level = levels[levelName];
            uniqueName = string.Format("{0}.[{1}]", level.UniqueName, uniqueName);

            // find the member you already have created
            IMember member;
            if(!level.TryGetMember(uniqueName, out member))
            {
                throw new InvalidOperationException(string.Format("No metadata found for member: {0}", uniqueName));
            }

            // create a single copy of this member for each tuple it participates
            member = ((Member) member).Clone();
            tuples.Add(new Infragistics.Olap.Data.Base.Tuple(new OlapElementCollection<IMember>(new List<IMember> { member })));

            // Repeat recursively for all children?
            if (flatPivotAxis.Children != null)
            {
                foreach (var child in flatPivotAxis.Children)
                {
                    ProcessFlatPivotAxis(child.Value, tuples, member, levelDepth + 1, uniqueName, levels);
                }
            }
        }

        /*
         * This bit seems relatively simple, just copy values one-to-one from my source
         * to the ICell array.
         */
        protected virtual ICell[,] BuildResultCells(FlatPivotTable flatPivotTable)
        {
            var cells = new ICell[flatPivotTable.YAxis.Width, flatPivotTable.XAxis.Width];
            for (var y = 0; y < cells.GetLength(0); y++)
            {
                for (var x = 0; x < cells.GetLength(1); x++)
                {
                    var value = flatPivotTable.Cells[y][x];
                    cells[y, x] = new Cell(value, value.ToString());
                }
            }
            return cells;
        }

        private void CreateLevelsForAxis(FlatPivotAxis axis, IHierarchy parentHierarchy, IDictionary<string, ILevel> levels)
        {
            string levelName = string.Format("Level{0}", axis.Level);
            if (!levels.ContainsKey(levelName))
            {
                FlatPivotLevel level = new FlatPivotLevel(levelName, levelName, string.Format("{0}.[{1}]", parentHierarchy.UniqueName, levelName), string.Empty, 0, parentHierarchy);
                levels.Add(levelName, level);
            }

            if (axis.Children != null && axis.Children.Count > 0)
            {
                foreach (KeyValuePair<object, FlatPivotAxis> keyValuePair in axis.Children)
                {
                    this.CreateLevelsForAxis(keyValuePair.Value, parentHierarchy, levels);
                }
            }
        }

        private void CreateMembersForLevels(FlatPivotAxis flatPivotAxis, IMember parentMember, int levelDepth, string uniqueNamePrefix, IDictionary<string, ILevel> levels)
        {
            // You code is modified now to be aware about the level where each member appears
            // Crude - I know, and all guesswork
            var name = flatPivotAxis.Header.ToString();
            var uniqueName = (uniqueNamePrefix != String.Empty ? uniqueNamePrefix + "_" : String.Empty) + flatPivotAxis.Header;
            var parentUniqueName = parentMember != null ? parentMember.UniqueName : String.Empty;
            var childCount = flatPivotAxis.Children != null ? flatPivotAxis.Children.Count : 0;

            string levelName = string.Format("Level{0}", flatPivotAxis.Level);
            ILevel level = levels[levelName];

            uniqueName = string.Format("{0}.[{1}]", level.UniqueName, uniqueName);
            IMember member;
            if (!level.TryGetMember(uniqueName, out member))
            {
                // Each "node" in my axis represents a tuple from what what I can gauge. Its not entirely
                // clear how the member I specify should be related to other members, but I'm guessing the 
                // level thats implied in my hierarchy comes into play.

                // Creating the member here...its a somewhat odd list of constructor parameters, in that its 
                // not clear what they should all really be in the grand scheme of things.
                member = new FlatPivotMember(
                    name,
                    uniqueName,
                    name,
                    parentUniqueName,
                    level.Name,			// Q: This should probably be set...but to what? A: For flat data it's up to you to give a descriptive name for a level.
                    levelDepth,				// Q: Guessing that I incrememt from 0 based on my hierarchy depth A: Correct
                    level,					// Q: Again, this might need setting, but its not clear how I define levels A: The level this member belongs to.
                    childCount,
                    false,					// How should these booleans really be set? A: Can leave all these set to false. Not used actually. Here is explanation about what these values mean when used with XMLA http://msdn.microsoft.com/en-us/library/windows/desktop/ms725398(v=vs.85).aspx
                    false,
                    false);

                // register a member for a level
                AddMember((FlatPivotLevel)level, member);
                ((FlatPivotLevel)level).MembersCount++;

                if (parentMember != null)
                {
                    // register a member as a child of another member
                    AddMember((FlatPivotMember)parentMember, member);
                    ((FlatPivotMember)parentMember).IsLoaded = true;
                }
            }

            // Repeat recursively for all children?
            if (flatPivotAxis.Children != null)
            {
                foreach (var child in flatPivotAxis.Children)
                {
                    this.CreateMembersForLevels(child.Value, member, levelDepth + 1, uniqueName, levels);
                }
            }
        }        
    }
}
